<script type="text/javascript">

/**
 * Tic-Tac-Toe static object controller namespace
 */
var TTT = {
   Keys: {
       NAME: "name",
       SCORE: "score"
   },
   RequestKeys: {
       VIEWERDATA: "viewer_data",
       VIEWER: "v",
       VIEWER_FRIENDS: "vf",
       VIEWER_PHOTOS: "vp"
   },
   Players: {
       Computer: "O",
       Human: "X"
   },
   /**
    * Actual Tic-Tac-Toe game engine.
    */
   Game: function(){

      var me = this;
      var cellbackEmpty = "#555";
      var cellbackWin = "#88E";
      var cellbackCustomBG = "";
      
      this.customBG = new TTT.CustomBackground();

      /**
       * Clear the game and reset
       */
      this.clear = function(){
         var i;
         for(i = 0; i < this.gameCells.length; i++){
            this.gameCells[i].innerHTML = "";

            if(this.customBG.usingCustomBG()){
               this.gameCells[i].style.backgroundColor = this.customBG.cellbackCustomBG;
            }
            else{
               this.gameCells[i].style.backgroundColor = cellbackEmpty;
            }
         }
         this.moves = new Array(9);
         this.hasWinner = false;
         this.winningCells = null;
         this.currentPlayer = TTT.Players.Human;
      }

      /**
       * Colors background on winning cells
       */
      this.markWinningCells = function(){
         if(!this.winningCells) return;
         for(var i = 0; i < this.winningCells.length; i++){
            this.gameCells[this.winningCells[i]].style.backgroundColor = cellbackWin;
         }
      }

      /**
       * Flag to indicate a winner was found
       */
      this.hasWinner = false;

      /**
       * Identify the cells in the winning line
       */
      this.winningCells = null;

      /**
       * Last person to win - player or computer
       */
      this.lastWinner = null;
      
      /**
        * The player who is currently allowed to make a move
        */
       this.currentPlayer = TTT.Players.Human;
      
      /**
       * The ID of your opponent
       */
      this.opponentId = null;
      
      function init(){
         me.displayBoardElem = document.getElementById("gameboard");
         me.gameCells = me.displayBoardElem.getElementsByTagName("td");
         me.moves = new Array(9);
         me.messageBlock = document.getElementById("messages");
         me.clear();
      }

      init();
   }
};

/**
 * Game object method defintions
 */
TTT.Game.prototype = {

   writeMessage: function(message, color){
       if(color){
           this.messageBlock.innerHTML += "<span style='color:" + color + ";'>" + message + "</span>";
       }
       else{
           this.messageBlock.innerHTML += message;
       }
   },

   /**
    * Makes a move and records the result.
    * Draws the move on the game board
    * @param {Object} player
    * @param {Object} position
    */
   makeMove : function(player, position){
       if(player !== this.currentPlayer) return;
       
       this.lookForWin();
       if(!this.hasWinner){
           if(!this.moves[position]){
               this.moves[position] = player;
               this._drawMove(player, position);
               this.lookForWin();
           }
           else{
               writeMessage("Invalid Move for " + player, "red");
           }
           
           this.currentPlayer = (player === TTT.Players.Human) ? TTT.Players.Computer : TTT.Players.Human;
       }
       if(this.hasWinner){
           this.markWinningCells();
       }
   },
   /**
    * Draws the player's mark in the cell
    * @param {Object} player
    * @param {Object} position
    */
   _drawMove: function(player, position){
       this.gameCells[position].innerHTML = player;
   },

   /**
    * Intense AI to compete in most difficult tick-tack-toe tournaments
    */
   makeComputerMove : function(){
       var die = 50;
       var pos;
       var found = false;
       var i=0;
       while(!found && i++ < die){
           pos = new Date().getMilliseconds() % 10;
           if(pos == 9) pos = 8;
           found = (!this.moves[pos]);
       }
       if(!found){
           for(i=0; i < 9; i++){
               if(!this.moves[i]){
                   this.makeMove(TTT.Players.Computer, i);
                   return;
               }
           }
           alert('no move');
           return;
       }
       else{
           this.makeMove(TTT.Players.Computer, pos);
       }
   },


   lookForWin: function(){
       //horizontal
       var i=0;
       for(i=0; i<9; i=i+3){
           this.hasWinner = this.matchMovePositions(i,i+1,i+2);
           if(this.hasWinner){
               this.winningCells = [i,i+1,i+2]
               return;
           }
       }
       
       //vertical
       for(i=0; i<3; i++){
           this.hasWinner = this.matchMovePositions(i,i+3,i+6);
           if(this.hasWinner){
               this.winningCells = [i,i+3,i+6]
               return;
           }
       }
       
       //diag
       if(!this.hasWinner){
           this.hasWinner = this.matchMovePositions(0,4,8);
           if(this.hasWinner){
               this.winningCells = [0,4,8]
           }
       }
       if(!this.hasWinner){
           this.hasWinner = this.matchMovePositions(2,4,6);
           if(this.hasWinner){
               this.winningCells = [2,4,6]
           }
       }

   },

   /**
    * Tests three positions to see if they match
    * @param {Object} a
    * @param {Object} b
    * @param {Object} c
    */
   matchMovePositions : function(a, b, c){
       if(this.moves[a]){
           return (this.moves[a] == this.moves[b] && this.moves[a] == this.moves[c]);
       }
       return false;
   }
};

TTT.CustomBackground = function(){
   this._hasCustomBG = false;
   this.photo = {};
   this.cellbackCustomBG = "";
   
   /**
    * Returns true/false based on if a custom user background is 
    * currently in use
    */
   this.usingCustomBG = function(){
      return this._hasCustomBG;
   };
   
   /**
    * Gets the background of the TTT board
    */
   this.getCustomBG = function(){
      var start = currentGame.displayBoardElem.style.background.indexOf("http");
      var end = currentGame.displayBoardElem.style.background.indexOf(")");
      
      return currentGame.displayBoardElem.style.background.substring(start, end);
   };
   
   /**
    * Sets the background of the TTT board to a custom user image
    */
   this.setCustomBG = function(photo){
      this.photo = photo;
      var img_src = photo.getField(MyOpenSpace.Photo.Field.IMAGE_URI);
      
      currentGame.displayBoardElem.style.background = "url(" + img_src + ")";
      this._hasCustomBG = true;
      
      for(i = 0; i < currentGame.gameCells.length; i++){
         currentGame.gameCells[i].style.backgroundColor = this.cellbackCustomBG;
      }
   };
}

TTT.LightBox = {
   _overlay:null,
   _light_box:null,
   _light_box_container:null,
   _is_init:false,
   
   _init:function(){
      this._overlay = document.getElementById("overlay");
      this._light_box = document.getElementById("light_box");
      this._light_box_container = document.getElementById("light_box_container");
      this._is_init = true;
   },
   
   show:function(){
      if(!this._is_init){
         this._init();
      }
      
      this._overlay.style.display = "block";
      this._light_box_container.style.display = "block";
   },
   hide:function(){
      this._overlay.style.display = "none";
      this._light_box_container.style.display = "none";
   },
   setContent:function(html){
      if(!this._is_init){
         this._init();
      }
      
      this._light_box.innerHTML = html;
   }
};

TTT.Tab = function(tab_dom, container, action){
   this.tab_dom = tab_dom;
   this.container = container;
   
   if(this.container){
      this.container.hide = function(){
         this.style.display = "none";
      }
      
      this.container.show = function(){
         this.style.display = "block";
      }
   }
   
   this.action = action;
};

TTT.Tabs = {
   _tabs:[],
   
   _selectedTab:null,
   
   _initTabs:function(){
      this._tabs[0] = new TTT.Tab(document.getElementById("tab0"), document.getElementById("play_container"), playClicked);
      this._tabs[1] = new TTT.Tab(document.getElementById("tab1"), document.getElementById("invite_container"), inviteClicked);
      this._tabs[2] = new TTT.Tab(document.getElementById("tab2"), document.getElementById("challenge_container"), challengeClicked);
      this._tabs[3] = new TTT.Tab(document.getElementById("tab3"), document.getElementById("set_background_container"), setBackgroundClicked);
   },
   
   getCurrentContainer:function(){
      return this._tabs[this._selectedTab].container;
   },
   
   getCurrentTab:function(){
      return this._tabs[this._selectedTab];
   },
   
   selectTab:function(index){
      if(0 === this._tabs.length){
         this._initTabs();
      }

      if(index !== this._selectedTab){
         this._tabs[index].tab_dom.className += " tab_selected";
         
         var selected = this._tabs[this._selectedTab];
         if(null !== this._selectedTab){
            var ind = selected.tab_dom.className.indexOf(" tab_selected");
            selected.tab_dom.className = selected.tab_dom.className.substr(0, ind);
            
            selected.container.hide();
         }
         this._selectedTab = index;
         
         this._tabs[index].container.show();
         this._tabs[index].action();
      }
   }
};

TTT.List = function(list_type, id_key, name_key, image_key, class_name, item_onclick){
   this.list_type = list_type;
   this.list = [];
   this.list_dom = {};
   this.first = 1;
   this.max = 20;
   this.total = 0;
   this.retries = 3;
   
   this.id_key = id_key;
   this.name_key = name_key;
   this.image_key = image_key;
   this.class_name = class_name;
   this.item_onclick = item_onclick;
};

TTT.ListTypes = {
   ALL_FRIENDS:"ALL_FRIENDS",
   HASAPP_FRIENDS:"HASAPP_FRIENDS",
   SET_BACKGROUND:"SET_BACKGROUND"
};

TTT.Lists = {
   _current:"",
   
   itemClicked:[],
   
   getCurrentList:function(){
      for(var key in this.lists){
         if(this.lists[key].list_type === this._current){
            return this.lists[key];
         }
      }
   },
   
   setCurrentList:function(list_type){
      this._current = list_type;
   },
   
   lists:{
      all_friends:new TTT.List(TTT.ListTypes.ALL_FRIENDS, opensocial.Person.Field.ID, opensocial.Person.Field.NICKNAME, opensocial.Person.Field.THUMBNAIL_URL, "friend", rsaInviteClicked),
      hasapp_friends:new TTT.List(TTT.ListTypes.HASAPP_FRIENDS, opensocial.Person.Field.ID, opensocial.Person.Field.NICKNAME, opensocial.Person.Field.THUMBNAIL_URL, "friend", rsmMessage),
      viewer_photos:new TTT.List(TTT.ListTypes.SET_BACKGROUND, MyOpenSpace.Photo.Field.PHOTO_ID, MyOpenSpace.Photo.Field.CAPTION, MyOpenSpace.Photo.Field.IMAGE_URI, "photo", photoClicked)
   },
   
   markup_format:"<div><img src='{0}' /></div>{1}",
   
   permCallback:function(response){
      if(!response.hadError()){
         TTT.Lists.retryRequest();
      }
   },
   
   retryRequest:function(){
      TTT.Lists.getCurrentList().retries--;
      window.setTimeout(TTT.Tabs.getCurrentTab().action, 250);
   }
};

TTT.Lists.callback = function(response){
   var current_list = TTT.Lists.getCurrentList();
   
   var retryRequest = function(){
      current_list.retries--;
      window.setTimeout(fetchFriends, 250);
   };
   
   // check for an error
   if(!response.hadError()){
      var data = null;
      
      // loop through the available keys to
      // find the actual data in the response
      for(var key in TTT.RequestKeys){
         if(response.get(TTT.RequestKeys[key])){
            data = response.get(TTT.RequestKeys[key]).getData();
            break;
         }
      }
      
      // something bad happened to it
      if(null === data){
         retryRequest();
         return;
      }
      else{
         // save the actual data to the TTT.List object
         current_list.list = data.asArray();
         
         // save the total number of items in the list
         current_list.total = data.getTotalSize();
         
         // draw the list
         TTT.Lists.draw();
      }
   }
   else{
      // if there was a permission issue
      if(data.getErrorCode() === opensocial.ResponseItem.Error.UNAUTHORIZED){
         var reason = "To set a custom background for the game board!";
         opensocial.requestPermission([MyOpenSpace.Permission.VIEWER_ACCESS_TO_PUBLIC_VIDEOS_PHOTOS], reason, TTT.Lists.permCallback);
      }
      // if there was an error and there are retries left, go for it
      else if(current_list.retries > 0){
         retryRequest();
         return;
      }
      // out of retries...
      else{
         log("Oops, there was an error! Try refreshing the page.");
      }
   }
};

TTT.Lists.draw = function(){
   var div, id, name, picture;
   
   // create a container to insert all the items into
   var all_items = document.createElement("div");
   
   var current_list = this.getCurrentList();
   
   // display a message if the list is empty
   if(0 === current_list.list.length){
      TTT.Tabs.getCurrentContainer().innerHTML = "Oops, no results to show!";
      return;
   }
   
   // add the pager markup
   all_items.innerHTML = TTT.Lists.Pager.getMarkUp();
   
   for(var i = 0; i < current_list.list.length; i++){
      id = current_list.list[i].getField(current_list.id_key);
      name = current_list.list[i].getField(current_list.name_key);
      picture = current_list.list[i].getField(current_list.image_key);

      // create the item's div element      
      div = document.createElement("div");
      div.className = current_list.class_name;
      div.onclick = current_list.item_onclick;
      div.list_index = id;
      div.innerHTML = this.markup_format.replace("{0}", picture).replace("{1}", name);
      
      // append it to the container
      all_items.appendChild(div);
      
      // save a reference to the element
      current_list.list_dom[id] = div;
   }
   
   // add a clearing div since we're using css floats
   div = document.createElement("div");
   div.className = "clear";
   all_items.appendChild(div);
   
   var save_first_child = TTT.Tabs.getCurrentContainer().firstChild.innerHTML;
   TTT.Tabs.getCurrentContainer().innerHTML = "<div>" + save_first_child + "</div>";
   
   // add all the items at once to the current tab, doing it all
   // at once means that we only update the DOM once
   TTT.Tabs.getCurrentContainer().appendChild(all_items);
};

TTT.Lists.Pager = {
   pager_wrap:"<div class='pager'>{0}</div>",
   pager_prev:"<span onclick='TTT.Lists.Pager.prev();'><< prev</span>&nbsp;",
   pager_next:"&nbsp;<span onclick='TTT.Lists.Pager.next();'>next >></span>",
   page_size:20,
   
   getMarkUp:function(){
      var current_list = TTT.Lists.getCurrentList();
      
      if(current_list.list.length < this.page_size && 
               1 === current_list.first){
         // if the length is less than the amount requested
         // there is only one page, no need for a pager
         return "";
      }
      
      var markup = "";
      
      if(current_list.first > this.page_size){
         // if the first index is greater than the size of
         // a page then there will be a previous page
         markup += this.pager_prev;
      }
      
      if(current_list.total > current_list.first + current_list.list.length - 1){
         // if the total number of records is greater than
         // the current index, plus the amount in the current
         // result set, minus 1, then there's another page.
         // e.g. first = 1, current set = 20, total = 55
         // 1 + 20 - 1 = 20 records fetched, < 55
         // e.g. first = 21, current set = 20, total = 55
         // 21 + 20 - 1 = 40 records fetched, < 55
         // e.g. first = 41, current set = 10, total = 55
         // 41 + 15 - 1 = 55 records fetched, == 55
         markup += this.pager_next;
      }
      
      if(markup.length > 0){
         markup = this.pager_wrap.replace("{0}", markup);
      }
      
      return markup;
   },
   prev:function(){
      var current_list = TTT.Lists.getCurrentList();
      
      current_list.first -= this.page_size;
      fetchFriends(current_list.list_type);
   },
   next:function(){
      var current_list = TTT.Lists.getCurrentList();
      
      current_list.first += this.page_size;
      fetchFriends(current_list.list_type);
   }
};

var currentGame = null;

function makePlayerMove(e){
   if(!e) var e = window.event;

   var cell = (e.target) ? e.target.getAttribute("gamecell") : e.srcElement.getAttribute("gamecell");
   currentGame.makeMove(TTT.Players.Human, cell);
   if(!currentGame.hasWinner){
       window.setTimeout(function(){
           currentGame.makeComputerMove();
       }, 500);
   }

}

/**
 * Function called by the onload handler and the entry point for the app
 */
function initializeGame(){
   getInitialData();
    
   if(currentGame){
      currentGame.clear();
   }
   else{
      gadgets.window.adjustHeight(3000);
      currentGame = new TTT.Game();
   }
}


var retries = 3;
var debug = true;

/**
 * Makes the initial call for the viewer's opensocial.Person object and requests all
 * fields supported by MySpace
 */
function getInitialData(){

   // returns an opensocial.DataRequest object
   var req = opensocial.newDataRequest();
   
   if(null === viewer){

      // used to specify what data is returned in the response
      var fields = [opensocial.Person.Field.ABOUT_ME, // string
                     opensocial.Person.Field.AGE, // number
                     opensocial.Person.Field.BODY_TYPE, // opensocial.BodyType
                     opensocial.Person.Field.BOOKS, // array of strings
                     opensocial.Person.Field.CHILDREN, // string
                     opensocial.Person.Field.CURRENT_LOCATION, // opensocial.Address
                     opensocial.Person.Field.DATE_OF_BIRTH, // Date
                     opensocial.Person.Field.DRINKER, // opensocial.Enum.Drinker
                     opensocial.Person.Field.ETHNICITY, // string
                     opensocial.Person.Field.GENDER, // opensocial.Enum.Gender
                     opensocial.Person.Field.HAS_APP, // boolean
                     opensocial.Person.Field.HEROES, // array of strings
                     opensocial.Person.Field.ID, // string
                     opensocial.Person.Field.INTERESTS, // array of strings
                     opensocial.Person.Field.JOBS, // array of opensocial.Organization
                     opensocial.Person.Field.LOOKING_FOR, // opensocial.Enum.LookingFor
                     opensocial.Person.Field.MOVIES, // array of strings
                     opensocial.Person.Field.MUSIC, // array of strings
                     opensocial.Person.Field.NAME, // opensocial.Name
                     opensocial.Person.Field.NETWORK_PRESENCE, // opensocial.Enum.Presence
                     opensocial.Person.Field.NICKNAME, // strings
                     opensocial.Person.Field.PROFILE_SONG, // opensocial.Url
                     opensocial.Person.Field.PROFILE_URL, // strings
                     opensocial.Person.Field.RELATIONSHIP_STATUS, // string
                     opensocial.Person.Field.RELIGION, // strings
                     opensocial.Person.Field.SEXUAL_ORIENTATION, // strings
                     opensocial.Person.Field.SMOKER, // opensocial.Enum.Smoker
                     opensocial.Person.Field.STATUS, // string
                     opensocial.Person.Field.THUMBNAIL_URL, // string
                     opensocial.Person.Field.TV_SHOWS, // array of strings
                     opensocial.Person.Field.URLS, // array of opensocial.Url
                     MyOpenSpace.Person.Field.MEDIUM_IMAGE, // opensocial.Url
                     MyOpenSpace.Person.Field.LARGE_IMAGE // opensocial.Url
                     ];
      
      // create an empty object to use for passing in the parameters,
      // the parameter here are addition Person fields from above
      var opt_params = {};
      
      // add the list of fields to the parameters
      opt_params[opensocial.DataRequest.PeopleRequestFields.PROFILE_DETAILS] = fields;
      
      // create a reqiest object, passing in the viewer, this will
      // specify we want the opensocial.Person for the viewer
      var viewer_req = req.newFetchPersonRequest(opensocial.IdSpec.PersonId.VIEWER, opt_params);
      
      // add the request to the queue and give it a key
      req.add(viewer_req, "v");
   }

   // start processing the requests and specify the callback function
   req.send(getDataCallback);
}

var viewer = null;

/**
 * The callback method for the initial request
 * @param {opensocial.DataResponse} data
 */
function getDataCallback(data){

   // first thing's first... always check if there was an error with your request
   if(!requestHadError(data)){

      // "v" was the key created in the request,
      // data is an object of type opensocial.DataResponse
      // data.get("v") returns an opensocial.ResponseItem
      // data.get("v").getData() will return the actual data,
      // in this case an opensocial.Person object
      viewer = data.get("v").getData();
   
      // now let's do something with all that data
      printPerson(document.getElementById("myinfo"));
      
      // unless the height of your app is fixed it's usually a good idea to call
      // gadgets.window.adjustHeight after you output content into the DOM as
      // some content could get cut off
      
      // this was removed in chapter 3, instead the app is resized to a
      // very large size right at the beginning
      
      //gadgets.window.adjustHeight();
   }
}

/**
 * Check if the response had an error, if it did, log it and if it was an INTERNAL_ERROR attempt to retry the request
 * @param {opensocial.DataResponse} data
 */
function requestHadError(data){

   // check the opensocial.DataResponse for the global error flag
   if(!data || data.hadError()){
   
      // find the specific opensocial.ResponseItem that had the error
      var ri = data.get(TTT.RequestKeys.VIEWER);
      
      if(ri && ri.hadError()){
      
         // output the error message
         log(ri.getErrorMessage());
         
         // check the error code, an INTERNAL_ERROR can simply mean network congestion or MySpace server instability
         if(opensocial.ResponseItem.Error.INTERNAL_ERROR === ri.getErrorCode()){
         
            // retry the request a certain number of times, make sure you don't create an infinite loop here!
            if(retries > 0){
               retries--;
               window.setTimeout(getInitialData, 1000);
            }
         }
      }
      
      return true;
   }
   
   return false;
}

/**
 * Output the viewer data onto the surface
 */
function printPerson(div){
   if(null !== viewer){
   
      var base_obj = [], num_set = 0;
      for(var i = 0; i < div.childNodes.length; i++){
         if(1 === div.childNodes[i].nodeType){
            base_obj[num_set] = div.childNodes[i];
            if(1 === num_set){
               break;
            }
            else{
               num_set++;
            }
         }
      }
      
      var img_div = base_obj[0].firstChild.firstChild;
      var name_div = base_obj[1].firstChild;
      var location_div = base_obj[1].childNodes[1];
      var gender_div = base_obj[1].childNodes[2];
   
      // you can set the src attribute of an <img> tag directly with THUMBNAIL_URL
      img_div.src = viewer.getField(opensocial.Person.Field.THUMBNAIL_URL);
      
      // getDisplayName is a shortcut for getField(opensocial.Person.Field.NICKNAME)
      name_div.innerHTML = viewer.getDisplayName();
      
      // get the viewer's status
      var status = viewer.getField(opensocial.Person.Field.STATUS);
      if(status && status.length > 0){
      
         // if the status has been set append it after the name
         name_div.innerHTML += " \"" + status + "\"";
      }
      
      // get the opensocial.Address object
      var location = viewer.getField(opensocial.Person.Field.CURRENT_LOCATION);
      
      // the Address object is used similarly to the Person object, both pass a
      // Field into a getField function
      location_div.innerHTML = location.getField(opensocial.Address.Field.REGION);
      
      // gender is an opensocial.Enum object
      var gender = viewer.getField(opensocial.Person.Field.GENDER);
      
      // getDisplayValue is defined by the specific container and can and
      // will differ between containers and is designed for displaying only
      gender_div.innerHTML = gender.getDisplayValue();
      
      // the response of getKey is defined by OpenSocial and therefore
      // you can compare the result to some known value
      if(gender.getKey() === opensocial.Enum.Gender.FEMALE){
         div.style.backgroundColor = "#fcf";
      }
      else{
         div.style.backgroundColor = "#09f";
      }
      
      /**
       * Takes an opensocial.Person.Field or MyOpenSpace.Person.Field, parses the data
       * out from the viewer object and formats the output to something that can be
       * displayed on the surface
       * @param {*.Person.Field} field
       * @param {boolean} add_break
       */
      var parsePersonField = function(field, add_break){
         var str = "";
         
         // get whatever data is stored in the viewer for the field, can be undefined,
         // a string, number, object, array or opensocial.Enum
         var val = viewer.getField(field);
         
         if(!val) return "";
         
         switch(field){
            case opensocial.Person.Field.ABOUT_ME:
            case opensocial.Person.Field.CHILDREN:
            case opensocial.Person.Field.ETHNICITY:
            case opensocial.Person.Field.ID:
            case opensocial.Person.Field.NICKNAME:
            case opensocial.Person.Field.PROFILE_URL:
            case opensocial.Person.Field.RELATIONSHIP_STATUS:
            case opensocial.Person.Field.RELIGION:
            case opensocial.Person.Field.SEXUAL_ORIENTATION:
            case opensocial.Person.Field.STATUS:
            case opensocial.Person.Field.THUMBNAIL_URL:
               str = val; // these are all the strings and are easiest to parse
               break;
            case opensocial.Person.Field.AGE:
               str = val + " years old"; // this is a number
               break;
            case opensocial.Person.Field.BODY_TYPE:
               // opensocial.BodyType, MySpace only returns BUILD and HEIGHT
               if(val.getField(opensocial.BodyType.Field.BUILD)){
                  str = "Build: " + val.getField(opensocial.BodyType.Field.BUILD);
               }
               if(val.getField(opensocial.BodyType.Field.HEIGHT)){
                  if(str !== ""){
                     str += "<br />";
                  }
                  str = "Height: " + val.getField(opensocial.BodyType.Field.HEIGHT);
               }
               break;
            case opensocial.Person.Field.BOOKS:
            case opensocial.Person.Field.HEROES:
            case opensocial.Person.Field.INTERESTS:
            case opensocial.Person.Field.MOVIES:
            case opensocial.Person.Field.MUSIC:
            case opensocial.Person.Field.TV_SHOWS:
               // array of strings, these will all be array of size 1 as these are free
               // form text fields in a MySpace profile, so you'll get a big blob of
               // unparseable text for these
               if(val.length > 0){
                  str = val[0]; 
               }
               break;
            case opensocial.Person.Field.CURRENT_LOCATION:
               // opensocial.Address, MySpace only returns REGION, COUNTRY and POSTAL_CODE
               str = val.getField(opensocial.Address.Field.REGION) + " ";
               str += val.getField(opensocial.Address.Field.COUNTRY) + " ";
               str += val.getField(opensocial.Address.Field.POSTAL_CODE);
               break;
            case opensocial.Person.Field.DATE_OF_BIRTH:
               str = "Born: " + val.toDateString(); // Date
               break;
            case opensocial.Person.Field.DRINKER:
            case opensocial.Person.Field.GENDER:
            case opensocial.Person.Field.LOOKING_FOR:
            case opensocial.Person.Field.NETWORK_PRESENCE:
            case opensocial.Person.Field.SMOKER:
               str = val.getDisplayValue(); // opensocial.Enum
               break;
            case opensocial.Person.Field.HAS_APP:
               str = (val) ? "Has the app installed" : "App not installed"; // boolean
               break;
            case opensocial.Person.Field.JOBS:
               // array of opensocial.Organization, MySpace only returns NAME and TITLE
               for(var i = 0; i < val.length; i++){
                  str += "Name: " + val[i].getField(opensocial.Organization.Field.NAME);
                  if(val[i].getField(opensocial.Organization.Field.TITLE)){
                     str += "<br />" + val[i].getField(opensocial.Organization.Field.TITLE) + "<br />";
                  }
               }
               break;
            case opensocial.Person.Field.NAME:
               // opensocial.Name, MySpace only returns UNSTRUCTURED
               str = val.getField(opensocial.Name.Field.UNSTRUCTURED);
               break;
            case opensocial.Person.Field.PROFILE_SONG:
            case MyOpenSpace.Person.Field.MEDIUM_IMAGE:
            case MyOpenSpace.Person.Field.LARGE_IMAGE:
               // opensocial.Url, MySpace returns ADDRESS and TYPE
               str = val.getField(opensocial.Url.Field.ADDRESS);
               break;
            case opensocial.Person.Field.URLS:
               // array of opensocial.Url, will only return the profile URL, so it's
               // better to use opensocial.Person.Field.PROFILE_URL here instead
               for(var i = 0; i < val.length; i++){
                  str += val[i].getField(opensocial.Url.Field.ADDRESS);
               }
               break;
            default:
               break;
         }
         
         if(add_break) str += "<br />";
         
         return str;
      }
      
      // MySpace provides a built in StringBuilder class which can take an initial string in the constructor,
      // and has append and toString functions, this can be easier to use in some cases than simple
      // concatenation
      var sb = new MyOpenSpace.StringBuilder("<div id='player_bio'><div id='player_bio_title'>Player Bio</div>");
      sb.append("<div style='margin:5px;'><div class='left' style='height:300px;'><img class='profile_image' src='");
      
      sb.append(parsePersonField(MyOpenSpace.Person.Field.MEDIUM_IMAGE, false));
      sb.append("' /></div><div>");
      
      var status = parsePersonField(opensocial.Person.Field.STATUS, true);
      if("" !== status){
         sb.append("<b>");
         sb.append(status);
         sb.append("</b>");
      }
      
      sb.append(parsePersonField(opensocial.Person.Field.NAME, true));
      sb.append(parsePersonField(opensocial.Person.Field.ID, true));
      sb.append(parsePersonField(opensocial.Person.Field.DATE_OF_BIRTH, true));
      sb.append(parsePersonField(opensocial.Person.Field.AGE, true));
      sb.append(parsePersonField(opensocial.Person.Field.CURRENT_LOCATION, true));
      sb.append(parsePersonField(opensocial.Person.Field.BODY_TYPE, true));
      sb.append(parsePersonField(opensocial.Person.Field.CHILDREN, true));
      
      var drinks = parsePersonField(opensocial.Person.Field.DRINKER, true);
      if("" !== drinks){
         sb.append("Drinks: " + drinks);
      }
      
      var smokes = parsePersonField(opensocial.Person.Field.SMOKER, true);
      if("" !== smokes){
         sb.append("Smokes: " + smokes);
      }
      
      sb.append(parsePersonField(opensocial.Person.Field.ETHNICITY, true));
      sb.append(parsePersonField(opensocial.Person.Field.GENDER, true));
      sb.append(parsePersonField(opensocial.Person.Field.HAS_APP, true));
      sb.append(parsePersonField(opensocial.Person.Field.NETWORK_PRESENCE, true));
      sb.append(parsePersonField(opensocial.Person.Field.RELATIONSHIP_STATUS, true));
      
      sb.append("</div></div><div class='clear'></div>");
      sb.append("<div id='bio_close'><a href='javascript:TTT.LightBox.hide();'>close...</a></div>");
      sb.append("</div>");

      TTT.LightBox.setContent(sb.toString());
   }
}

function openProfile(){
   // profile URL is a simple string that can go directly into the src
   // attribute of an <img> tag, or, like here, directly into window.open()
   window.open(viewer.getField(opensocial.Person.Field.PROFILE_URL));
}

/**
 * Output an error message if debug mode is enabled
 * @param {String} message
 */
function log(message){
   var errFeed = document.getElementById("messages");
   if(errFeed){
      errFeed.innerHTML += message + "<br />";
   }
}

function rsmWrapper(id, subject, body, type, callback){
   var param = {};
   param[opensocial.Message.Field.TYPE] = type;
   param[opensocial.Message.Field.TITLE] = subject;
   
   var message = opensocial.newMessage(body, param);
   
   opensocial.requestSendMessage(id, message, callback);
}

function raiseActivity(){
   // create the parameters
   var param = {};
   
   // required template name
   param[opensocial.Activity.Field.TITLE_ID] = "x_and_y_started_game";
   
   // the actual parameter values
   param[opensocial.Activity.Field.TEMPLATE_PARAMS] = {
      "opponent" : currentGame.opponentId,
      "params" : "{\"from\":\"act\"}"
   };

   // check if a custom background is set to use for
   // a media item
   if(currentGame.customBG.usingCustomBG()){
      // get the photo object
      var photo = currentGame.customBG.photo;
      
      // parse out the URI
      var uri = photo.getField(MyOpenSpace.Photo.Field.PHOTO_URI);
      
      // create the opensocial.MediaItem object
      var media_item = opensocial.newMediaItem("image/jpeg", uri)
      
      // stick it in an array
      var media_item_array = [ media_item ];
      
      // insert the array into the parameters
      param[opensocial.Activity.Field.MEDIA_ITEMS] = media_item_array;
   }
   
   // create the opensocial.Activity object
   var activity = opensocial.newActivity(param);
   
   // raise the activity!
   opensocial.requestCreateActivity(activity, 
         opensocial.CreateActivityPriority.HIGH, raiseActivityCallback);
}

function raiseActivityCallback(response) {
   // check for an error
   if(response.hadError()){
   
      // was the error a permission issue?
      if(response.getErrorCode() === 
            opensocial.ResponseItem.Error.UNAUTHORIZED){
            
         // beg for permission
         var reason = "To inform your friends of your upcoming match!";
         
         // pick which permission
         // STICK IT IN AN ARRAY!!
         var perm = [MyOpenSpace.Permission.VIEWER_SEND_UPDATES_TO_FRIENDS];
         
         // pretty please...
         opensocial.requestPermission(perm, reason, actPermCallback);
      }
      else{
         // some other error
         log("Oops, there was an error, try refreshing!");
      }
   }
}

function actPermCallback(response){
   // was permission granted?
   if(!response.hadError()){
      // yay!
      raiseActivity();
   }
}

// version using the default template
function rsmNotification(recipient, game_id){
   // setup all the data we'll need
   var body = "${sender} has finished their move ";
   body += "in ${app}, it's your turn!";
   var url = MyOpenSpace.NotificationButton.UrlTypes.CANVAS;
   var button_params = {
      "from" : "noti",
      "action" : "play",
      "game_id" : game_id
   };
   var button_text = "Play Now!";
   
   var param = {};
   
   // create a MyOpenSpace.NotificationButton object
   var button1 = MyOpenSpace.newNotificationButton(url, 
         button_text, button_params);
   
   var param = {};
   
   param[MyOpenSpace.Notification.Field.BODY] = body;
   param[MyOpenSpace.Notification.Field.BUTTONS] = [button1];
   
   // create the MyOpenSpace.Notification object
   var notification = MyOpenSpace.newNotification(param);
   
   // send it off!
   MyOpenSpace.requestCreateNotification(recipient, 
         notification, rsmNotificationCallback);
}

// version with templates defined
function rsmNotificationV2(recipient, game_id){
   // defined template:
   // Hey ${opponent}, ${sender} made their move, it's your turn!
   
   // setup all the data we'll need
   var template = "chad_template";
   var url = MyOpenSpace.NotificationButton.UrlTypes.CANVAS;
   var button_params = {
      "from" : "noti",
      "action" : "play",
      "game_id" : game_id
   };
   var button_text = "Play Now!";
   var template_params = { "opponent" : recipient };
   
   var param = {};
   
   param[MyOpenSpace.NotificationButton.Field.URL] = url;
   param[MyOpenSpace.NotificationButton.Field.PARAMS] = button_params;
   param[MyOpenSpace.NotificationButton.Field.TEXT] = button_text;
   
   // create a MyOpenSpace.NotificationButton object
   var button1 = MyOpenSpace.newNotificationButton(param);
   
   param = {};
   
   param[MyOpenSpace.Notification.Field.TITLE_ID] = template;
   param[MyOpenSpace.Notification.Field.BUTTONS] = [button1];
   param[MyOpenSpace.Notification.Field.TEMPLATE_PARAMS] = template_params;
   
   // create the MyOpenSpace.Notification object
   var notification = MyOpenSpace.newNotification(param);
   
   // send it off!
   MyOpenSpace.requestCreateNotification(recipient, 
         notification, rsmNotificationCallback);
}

function rsmNotificationCallback(response){}

function rsmMessage(){
   var current_list = TTT.Lists.getCurrentList();
   var id = this.list_index;
   TTT.Lists.itemClicked = id;
   
   var this_app = opensocial.getEnvironment().currentApplication;
   var profile_link = 
         this_app.getField(MyOpenSpace.Application.Field.PROFILE_URL);
   var image_link = 
         this_app.getField(MyOpenSpace.Application.Field.ICON_LARGE);
   
   var subject = "I challenge you to a TTT duel!";
   
   var name = "";
   for(var i = 0; i < current_list.list.length; i++){
      if(current_list.list[i].getId() == id){
         name = current_list.list[i].getDisplayName();
         break;
      }
   }
   
   name = ("" === name) ? name : " " + name;
   
   var body = "Hey" + name + "! You've been challenged to ";
   body += "a game of Tic-Tac-Toe, click <a href='" + profile_link + "'>";
   body += "here</a> to accept the challenge!<center>";
   body += "<img src='" + image_link + "' /></center>";
   
   rsmWrapper(id, subject, body, 
         opensocial.Message.Type.PRIVATE_MESSAGE, rsmMessageCallback);
}

function rsmMessageCallback(response){
   var div = TTT.Tabs.getCurrentContainer().firstChild;
   
   if(response && !response.hadError()){
      if(0 === response.getData()){
         if(div.innerHTML.indexOf("challenge cancelled...") < 0){
            div.innerHTML += "challenge cancelled...";
         }
      }
      else if(1 === response.getData()){
         div.innerHTML = "challenge sent!";
      }
   }
   else{
      log("Oops, there was an error, try refreshing the page!");
   }
}

function rsmBulletin(){
   var this_app = opensocial.getEnvironment().currentApplication;
   var profile_link = 
         this_app.getField(MyOpenSpace.Application.Field.PROFILE_URL);
   var image_link = 
         this_app.getField(MyOpenSpace.Application.Field.ICON_LARGE);
   
   var subject = "Social Tic-Tac-Toe is Here!";
   
   var body = "Hey everyone! I'm playing TTT online with my friends, come ";
   body += "<a href='" + profile_link + "'>play a game</a> with me!";
   body += "<center><img src='" + image_link + "' /></center>";
   
   rsmWrapper(opensocial.IdSpec.PersonId.VIEWER, subject, body, 
         opensocial.Message.Type.NOTIFICATION, rsmBulletinCallback);
}

function rsmBulletinCallback(response){
   var div = TTT.Tabs.getCurrentContainer().firstChild;
   
   if(response && !response.hadError()){
      if(0 === response.getData()){
         if(div.innerHTML.indexOf(" cancelled...") < 0){
            div.innerHTML += " cancelled...";
         }
      }
      else if(1 === response.getData()){
         div.innerHTML = "bulletin sent";
      }
   }
   else{
      log("Oops, there was an error with the bulletin, try refreshing the page!");
   }
}

function rsaInviteClicked(){
   var id = this.list_index;
   
   if(TTT.Lists.itemClicked.constructor === Array){
      var found = false;
      for(var i = 0; i < TTT.Lists.itemClicked.length; i++){
         if(TTT.Lists.itemClicked[i] === id){
            found = true;
         }
      }
      
      if(!found){
         TTT.Lists.itemClicked.push(id);
      }
   }
   else{
      TTT.Lists.itemClicked = [id];
   }
   
   var clicked = TTT.Lists.getCurrentList().list_dom[id];
   clicked.style.backgroundColor = "yellow";
}

function requestShareAppWrapper(id, body, callback){
   // create the message object
   var reason = opensocial.newMessage(body);

   // initiate the share app modal
   opensocial.requestShareApp(id, reason, callback);
}

function rsaInviteButtonClicked(){
   // TTT.Lists.itemClicked contains the list of friends
   // that were clicked
   if(TTT.Lists.itemClicked.length > 0){
   
      // take the ID of the first friend that was clicked
      var id = TTT.Lists.itemClicked[0];
      
      // create some catchy body text
      var body = "Hi [recipient]... [sender] wants you to come";
      body += " play Tic-Tac-Toe, add [app] now!";
      
      // then pass the values into our wrapper
      requestShareAppWrapper(id, body, rsaWrapperCallback);
   }
}

function rsaWrapperCallback(response){
   // get the first clicked friend
   var current_list = TTT.Lists.getCurrentList();
   clicked = current_list.list_dom[TTT.Lists.itemClicked[0]];
   
   // check for some kind of bad response, and show an error message
   if(!clicked || !response || response.hadError()
             || -1 === response.getData()){
      log("Oops, there was an error, try refreshing the page!");
   }
   else{
   
      // was the popup cancelled?
      if(0 === response.getData()){
      
         // if so, stop looping through the list and revert
         // all the friend boxes to a white background
         for(var i = 0; i < TTT.Lists.itemClicked.length; i++){
            clicked = current_list.list_dom[TTT.Lists.itemClicked[i]];
            clicked.style.backgroundColor = "white";
         }
         
         // reset the list
         TTT.Lists.itemClicked = [];
      }
      else{
      
         // otherwise the share request was sent,
         // remove the first friend
         TTT.Lists.itemClicked.splice(0, 1);
         
         // remove the friend box from the DOM to
         // make it clear it was sent
         if(clicked.parentNode){
            clicked.parentNode.removeChild(clicked);
         }
         
         // start the process again
         rsaInviteButtonClicked();
      }
   }
}

function playClicked(){}

function setBackgroundClicked(){
   fetchPhotos(TTT.ListTypes.SET_BACKGROUND);
}

function inviteClicked(){
   fetchFriends(TTT.ListTypes.ALL_FRIENDS);
}

function challengeClicked(){
   fetchFriends(TTT.ListTypes.HASAPP_FRIENDS);
}

function photoClicked(){
   var item_container = TTT.Lists.getCurrentList().list_dom[this.list_index];
   var img_src = item_container.firstChild.firstChild.src;
   
   item_container.style.backgroundColor = "yellow";
   window.setTimeout(function(){ item_container.style.backgroundColor = "white"; }, 3000);
   
   var current_list = TTT.Lists.getCurrentList(); 
   
   var photo = null;
   for(var i = 0; i < current_list.list.length; i++){
      if(current_list.list[i].getField(current_list.id_key) === this.list_index){
         photo = current_list.list[i];
      }
   }
   
   if(photo !== null){
      currentGame.customBG.setCustomBG(photo);
   }
   else{
      log("Oops! Something went wrong with your photo selection, try refreshing.");
   }
}

function fetchPhotos(list_type){
   TTT.Lists.setCurrentList(list_type);
   var current_list = TTT.Lists.getCurrentList();
   
   fetchPhotosList(current_list.first, current_list.max, TTT.Lists.callback);
}

function fetchFriends(list_type){
   TTT.Lists.setCurrentList(list_type);
   var current_list = TTT.Lists.getCurrentList();
   
   var filter = "";
   if(TTT.ListTypes.HASAPP_FRIENDS === list_type){
      filter = opensocial.DataRequest.FilterType.HAS_APP;
   }
   
   fetchFriendList(current_list.first, current_list.max, TTT.Lists.callback, filter);
}

function fetchFriendList(first, max, callback, filter, sort){
   // create the IdSpec object
   var params = {};
   params[opensocial.IdSpec.Field.USER_ID] = 
         opensocial.IdSpec.PersonId.VIEWER;
   params[opensocial.IdSpec.Field.NETWORK_DISTANCE] = 1;
   var idspec = opensocial.newIdSpec(params);
   
   params = {};
   
   // set the paging parameters
   if(first){
      params[opensocial.DataRequest.PeopleRequestFields.FIRST] = first;
   }
   
   if(max){
      params[opensocial.DataRequest.PeopleRequestFields.MAX] = max;
   }
   
   
   // add any filters
   if(filter && filter.length > 0){
      params[opensocial.DataRequest.PeopleRequestFields.FILTER] = filter;
   }
   
   // add any sorts
   if(sort && sort.length > 0){
      params[opensocial.DataRequest.PeopleRequestFields.SORT_ORDER] = sort;
   }
   
   // create the DataRequest object
   var request = opensocial.newDataRequest();
   
   // add the request to the queue
   request.add(request.newFetchPeopleRequest(idspec, params), 
         TTT.RequestKeys.VIEWER_FRIENDS);
         
   // send it off
   request.send(callback);
}

// fetches the viewer's photo albums
function fetchViewerPhotoAlbums(first, max, album_id, callback){
   // create the IdSpec object
   var params = {};
   params[opensocial.IdSpec.Field.USER_ID] = 
         opensocial.IdSpec.PersonId.VIEWER;
   params[opensocial.IdSpec.Field.NETWORK_DISTANCE] = 0;
   var idspec = opensocial.newIdSpec(params);
   
   // create the DataRequest object
   var request = opensocial.newDataRequest();
   
   params = {};
   
   // set the paging parameters
   if(first){
      params[opensocial.DataRequest.AlbumRequestFields.FIRST] = first;
   }
   
   if(max){
      params[opensocial.DataRequest.AlbumRequestFields.MAX] = max;
   }
   
   // pick an album, album_id is an array of album IDs
   if(album_id){
      params[opensocial.Album.Field.ID] = album_id;
   }
   
   // force it to images
   params[opensocial.Album.Field.MEDIA_TYPE] = 
         opensocial.MediaItem.Type.IMAGE;
   
   // add the request to the queue
   request.add(request.newFetchAlbumsRequest(idspec, params), 
         "albums");
         
   // send it off
   request.send(callback);
}

// fetches the viewer's photos
function fetchPhotosList(first, max, callback, media_id){
   // create the IdSpec object
   var params = {};
   params[opensocial.IdSpec.Field.USER_ID] = 
         opensocial.IdSpec.PersonId.VIEWER;
   params[opensocial.IdSpec.Field.NETWORK_DISTANCE] = 0;
   var idspec = opensocial.newIdSpec(params);
   
   // create the DataRequest object
   var request = opensocial.newDataRequest();
   
   params = {};
   
   // set the paging parameters
   if(first){
      params[opensocial.DataRequest.MediaItemRequestFields.FIRST] = first;
   }
   
   if(max){
      params[opensocial.DataRequest.MediaItemRequestFields.MAX] = max;
   }
   
   // pick a media item, media_id is an array of media IDs
   if(media_id){
      params[opensocial.MediaItem.Field.ID] = media_id;
   }
   
   // force it to images
   params[opensocial.MediaItem.Field.MEDIA_TYPE] = 
         opensocial.MediaItem.Type.IMAGE;
   
   // add the request to the queue
   request.add(request.newFetchAlbumsRequest(idspec, params), 
         TTT.RequestKeys.VIEWER_PHOTOS);
         
   // send it off
   request.send(callback);
}

// updates a media item for the viewer
function updateMediaItem(album_id, media_id, newTitle, 
         newPic, newDesc, callback){
         
   // create the IdSpec object
   var params = {};
   params[opensocial.IdSpec.Field.USER_ID] = 
         opensocial.IdSpec.PersonId.VIEWER;
   params[opensocial.IdSpec.Field.NETWORK_DISTANCE] = 0;
   var idspec = opensocial.newIdSpec(params);
   
   // create the DataRequest object
   var request = opensocial.newDataRequest();
   
   var fields = {};
   
   // set the new fields
   fields[opensocial.MediaItem.Field.TITLE] = newTitle;
   fields[opensocial.MediaItem.Field.THUMBNAIL_URL] = newPic;
   fields[opensocial.MediaItem.Field.DESCRIPTION] = newDesc;
   
   // add the request to the queue
   request.add(request.newUpdateMediaItemRequest(idspec, 
         album_id, media_id, fields), "update_mi");
         
   // send it off
   request.send(callback);
}

// uploads an image to the specified album
function uploadMediaItem(album_id){
   opensocial.requestUploadMediaItem(album_id, uploadMediaItemCallback);
}

// handles the response
function uploadMediaItemCallback(response){
   if(!response || response.hadError()){
      // error code
      var error_code = response.getErrorCode();
      
      // comma delimited list of failed files
      var error_message = response.getErrorMessage();
      
      // array of failed files
      var failed_files = error_message.split(",");
      
      // retry request? update UI?
   }
   else{
      // array of MediaItem objects
      var mi_array = response.getData();
      
      var div = document.getElementById("messages");
      var url = opensocial.MediaItem.Field.THUMBNAIL_URL;
      
      for(var i = 0; i < mi_array.length; i++){
         div.innerHTML += "<img src='";
         div.innerHTML += mi_array[i].getField(url);
         div.innerHTML += "' /><br />";
      }
   }
}

// fetches the app data for owner or viewer
function fetchPersonAppData(id, callback){

   // create an empty object to use for passing in the parameters
   var params = {};
   
   var keys = ["key1", "key2", "key3"];
   
   // add the list of fields to the parameters
   params[opensocial.DataRequest.PeopleRequestFields.APP_DATA] = keys;
   
   // id will be either:
   // opensocial.IdSpec.PersonId.VIEWER
   // or:
   // opensocial.IdSpec.PersonId.OWNER
   var appdata_req = req.newFetchPersonRequest(id, params);
   
   // add the request to the queue and give it a key
   req.add(appdata_req, "app_data");
   
   // send it off
   req.send(callback);
}

// parse the app data
function getAppData(response){

   // first check for an error
   if(!response.hadError()){
   
      // person is now an opensocial.Person object
      var person = response.get("app_data").getData();
      
      var val1 = person.getAppData("key1");
      var val2 = person.getAppData("key2");
      var val3 = person.getAppData("key3");
   }
   else{
      // retry the request?
   }
}

//register to look for initial data
gadgets.util.registerOnLoadHandler(initializeGame);
gadgets.util.registerOnLoadHandler(function(){ TTT.Tabs.selectTab(0); });

</script>

<div id="overlay"></div>
<div id="light_box_container"><div id="light_box"></div></div>

<div class="container">
   <h1>OpenSocial Tic-Tac-Toe</h1>
   
   <div>
      <div class="left tab_spacer small_width">&nbsp;</div>
      <div id="tab0" class="left tab" onclick="TTT.Tabs.selectTab(0);">Play</div>
      <div id="tab1" class="left tab" onclick="TTT.Tabs.selectTab(1);">Invite</div>
      <div id="tab2" class="left tab" onclick="TTT.Tabs.selectTab(2);">Challenge</div>
      <div id="tab3" class="left tab tab_last" onclick="TTT.Tabs.selectTab(3);">Set Background</div>
      <div class="left tab_spacer large_width">&nbsp;</div>
      <div class="clear"></div>
   </div>
</div>

<div id="play_container" class="container">
   <div class="left">
       <table id="gameboard" onclick="makePlayerMove(event)">

           <tr>
               <td gamecell="0"></td>
               <td gamecell="1"></td>
               <td gamecell="2"></td>
           </tr>
           <tr>
               <td gamecell="3"></td>
               <td gamecell="4"></td>
               <td gamecell="5"></td>

           </tr>
           <tr>
               <td gamecell="6"></td>
               <td gamecell="7"></td>
               <td gamecell="8"></td>
           </tr>
       </table>
   </div>

   <div id="right_column">
      <div id="myinfo" class="player_info right_column">
         <div class="left"><a href="javascript:openProfile();"><img class="profile_image" src="http://x.myspacecdn.com/images/no_pic.gif" /></a></div>
         <div><div></div><div></div><div></div><div class="more"><a href="javascript:TTT.LightBox.show();">More...</a></div></div>
         <div class="clear"></div>
      </div>
      
      <div id="opponentinfo" class="player_info right_column hide">
         <div class="left"><a href="javascript:openProfile();"><img class="profile_image" src="http://x.myspacecdn.com/images/no_pic.gif" /></a></div>
         <div><div></div><div></div><div></div><div class="more"><a href="javascript:TTT.LightBox.show();">More...</a></div></div>
         <div class="clear"></div>
      </div>
      
      <div class="clear"></div>
   
      <div id="game_list" class="right_column hide"></div>
   </div>

   <div class="clear"></div>

   <button onclick="initializeGame();">New Game</button>&nbsp;

</div>

<div id="invite_container" class="container hide"><div><button onclick="rsmBulletin();">Send a Bulletin!</button>&nbsp;<button onclick="rsaInviteButtonClicked();">Send Invite(s)</button></div></div>

<div id="challenge_container" class="container hide"><div></div></div>

<div id="set_background_container" class="container hide"><div></div></div>

<div id="messages"></div>

<style>
   img
   {
      border:0;
   }
   body
   {
      margin:0;
      padding:0;
      font-family:Verdana;
      font-size:12pt;
   }
   
   #gameboard
   {
   	margin:10px;
      border-collapse: collapse;
      border:2px solid black;
      padding:5px;
      margin-bottom:5px;
      empty-cells:show;
      font-size: 40pt;
      font-family:Verdana;
      font-weight:bold;
      color:#fff;
      cursor:pointer;
   }
   #gameboard td
   {
      color:#fff;
      border:1px solid white;
      text-align:center;
      width:120px;
      height:120px;
   }
   .container
   {
   	margin:10px;
   }
   .tab
   {
      border:solid 1px black;
      border-right:0;
      padding:5px 5px 4px;
      cursor:pointer;
      background-color:#ddd;
   }
   .tab_last
   {
      border-right:solid 1px black;
   }
   .tab_spacer
   {
      border-bottom:solid 1px black;
      padding:5px;
   }
   .small_width
   {
      width:40px;
   }
   .large_width
   {
      width:50%;
   }
   .tab_selected
   {
      border-bottom:0;
      background-color:white;
   }
   .friend
   {
      border:solid 1px black;
      margin:5px;
      padding:5px;
      float:left;
      width:90px;_width:102px;
      overflow:hidden;
      text-align:center;
      height:160px;
      cursor:pointer;
   }
   .photo
   {
      border:solid 1px black;
      margin:5px;
      padding:5px;
      float:left;
      width:130px;_width:142px;
      overflow:hidden;
      text-align:center;
      height:130px;_height:142px;
      cursor:pointer;
   }
   .photo img
   {
      width:130px;
      height:110px;
   }
   .pager
   {
      font-size:9pt;
   }
   .pager span
   {
      color:blue;
      cursor:pointer;
   }
   #messages
   {
      height:20px;
      overflow:auto;
      color:Red;
   }
   #right_column
   {
      float:right;
      width:500px;
   }
   .right_column
   {
      float:right;
      margin:5px;
      width:100%;
   }
   .player_info
   {
      border:solid 2px black;
      padding:5px;
   }
   .profile_image
   {
      margin:5px;
      border:solid 1px black;
   }
   .more a
   {
      line-height:40px;
      padding:5px;
      border:solid 1px black;
      cursor:pointer;
      text-decoration:none;
      color:Black;
      background-color:White;
   }
   #overlay
   {
   	display:none;
   	width:100%;
   	height:100%;
   	background-color:Black;
   	left:0;
   	top:0;
   	z-index:100000;
   	position:absolute;
   	opacity:0.6;
   	filter: alpha(opacity=60);
   }
   #light_box_container
   {
   	width:100%;
   	left:0;
   	position:absolute;
   	top:70px;
   	z-index:100001;
   	text-align:center;
   	display:none;
   }
   #light_box
   {
   	margin:auto;
   	width:650px;
   	background-color:White;
   	border:solid 4px black;
   	text-align:left;
   }
   #player_bio
   {
   	margin:5px;
   }
   #player_bio_title
   {
   	border-bottom:solid 1px black;
   	font-weight:bold;
   	padding:1px 0 5px 0;
   	text-align:center;
   }
   #bio_close
   {
   	border-top:solid 1px black;
   	margin-top:5px;
   	padding:3px;
   	text-align:center;
   }
   #bio_close a
   {
   	text-decoration:none;
   }
   .clear
   {
      clear:both;
   }
   .left
   {
      float:left;
   }
   .right
   {
      float:right;
   }
   .hide{
   
      display:none;
   }
</style>